﻿#region

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Configuration;
using System.Threading;
using Newtonsoft.Json.Linq;
using PTCN.CrossPlatform.BettingGame.CardGameFramework.Config;
using PTCN.CrossPlatform.BettingGame.DataAccess;
using PTCN.CrossPlatform.BettingGame.DataAccess.DAO;
using PTCN.CrossPlatform.BettingGame.DataAccess.Types;
using PTCN.CrossPlatform.BettingGame.Utilities;
using PTCN.CrossPlatform.BettingGame.Utilities.IpAddress;
using PTCN.CrossPlatform.BettingGame.Bingo.Models;

#endregion

namespace PTCN.CrossPlatform.BettingGame.Bingo.Controllers
{
    public class PlayerHandler
    {
        // Singleton instance
        private static readonly Lazy<PlayerHandler> _instance = new Lazy<PlayerHandler>(() => new PlayerHandler());
        private readonly InnerPlayerHandler<SediePlayer> _inner = new InnerPlayerHandler<SediePlayer>();

        public static InnerPlayerHandler<SediePlayer> Instance
        {
            get { return _instance.Value._inner; }
        }

        private PlayerHandler()
        {
        }

        public class InnerPlayerHandler<T> where T : BettingGamePlayer
        {
            // Singleton instance       
            private readonly ConcurrentDictionary<long, T> _players;
            private readonly IAccountDAO _accountDAO;

            public InnerPlayerHandler()
            {
                _players = new ConcurrentDictionary<long, T>();
                _accountDAO = AbstractDAOFactory.Instance().CreateAccountDAO();
            }

            public void RemoveQuiters()
            {
                foreach (var player in _players.Values)
                {
                    long key = player.AccountID;
                    long idletime = player.IdleTime();
                    bool condition3 = player.SessionID > 0 && idletime >= ConfigTimeout.MaxIdleWaiting;

                    if (condition3)
                    {
                        var session = SessionHandler.Instance.GetSession(player.SessionID);
                        if (session != null && session.ContainPlayer(key))
                        {
                            if (session.IsPlaying || session.IsComfirming)
                            {
                                session.RegisterLeaveGame(key);
                                continue;
                            }
                            session.RemovePlayer(key);
                        }
                        else
                        {
                            player.ResetBeforeQuitRoom();
                        }
                    }

                    bool condition1 = player.SessionID < 1 && player.ConnectionStatus == ConnectionStatus.DISCONNECTED;
                    bool condition2 = player.SessionID < 1 && idletime >= ConfigTimeout.MaxIdleLobby;
                    if (!condition1 && !condition2) continue;
                    RemovePlayer(key);
                    NLogManager.LogMessage(string.Format("RemoveQuiters - Remove from lobby: {0}", key));
                }
            }

            #region dictionary_methods

            private T CreatePlayer(params object[] args)
            {
                return (T) Activator.CreateInstance(typeof (T), args);
            }

            public bool Contains(long accountId)
            {
                return _players.ContainsKey(accountId);
            }

            public T GetPlayer(long accountId)
            {
                if (accountId < 1)
                {
                    return null;
                }

                T player = null;
                if (Contains(accountId))
                {
                    _players.TryGetValue(accountId, out player);
                }
                return player;
            }

            public bool AddPlayer(T player)
            {
                if (player == null)
                {
                    return false;
                }

                if (_players.ContainsKey(player.AccountID))
                {
                    AccountDb oldAccount = player.Account;
                    //update account
                    if (!Monitor.TryEnter(oldAccount, 5000)) return true;
                    try
                    {
                        Copy(player.Account, oldAccount);
                    }
                    finally
                    {
                        Monitor.Exit(oldAccount);
                    }
                    return true;
                }
                else
                {
                    return _players.TryAdd(player.AccountID, player);
                }
            }

            public T AddPlayer(AccountDb account)
            {
                if (account == null)
                {
                    return null;
                }
                if (account.AccountID < 1)
                {
                    return null;
                }

                T player = null;
                if (_players.ContainsKey(account.AccountID))
                {
                    player = GetPlayer(account.AccountID);
                    AccountDb oldAccount = player.Account;
                    //update account
                    if (!Monitor.TryEnter(oldAccount, 5000)) return player;
                    try
                    {
                        Copy(player.Account, oldAccount);
                    }
                    finally
                    {
                        Monitor.Exit(oldAccount);
                    }
                    return player;
                }
                else
                {
                    player = CreatePlayer(account);
                    if (player == null) return null;
                    AddPlayer(player);
                    return player;
                }
            }

            public T AddPlayer(long accountId, string username)
            {
                if (accountId < 1 || String.IsNullOrEmpty(username))
                {
                    return null;
                }

                if (_players.ContainsKey(accountId))
                {
                    return GetPlayer(accountId);
                }

                T player = null;
                try
                {
                    AccountDb account = _accountDAO.GetAccount(accountId, username);
                    return AddPlayer(account);
                }
                catch (Exception ex)
                {
                    NLogManager.PublishException(ex);
                }
                return player;
            }

            public bool RemovePlayer(long accountId)
            {
                T player = null;
                return _players.TryRemove(accountId, out player);
            }

            public int Count()
            {
                return _players.Count;
            }

            #endregion dictionary_methods

            #region update_from_db

            public void Update(long accountId)
            {
                try
                {
                    if (!_players.ContainsKey(accountId))
                    {
                        return;
                    }
                    T player = null;
                    if (!_players.TryGetValue(accountId, out player))
                    {
                        return;
                    }

                    AccountDb oldAccount = player.Account;
                    AccountDb account = _accountDAO.GetAccount(accountId, oldAccount.UserName);
                    //update account
                    if (!Monitor.TryEnter(oldAccount, 5000)) return;
                    try
                    {
                        Copy(account, oldAccount);
                    }
                    finally
                    {
                        Monitor.Exit(oldAccount);
                    }
                }
                catch (Exception ex)
                {
                    NLogManager.PublishException(ex);
                }
            }

            public void UpdateAll(List<long> accounts)
            {
                try
                {
                    for (int i = 0; i < accounts.Count; i++)
                    {
                        Update(accounts[i]);
                    }
                }
                catch (Exception ex)
                {
                    NLogManager.PublishException(ex);
                }
            }

            public AccountDb UpdateAndGet(long accountId)
            {
                try
                {
                    if (!_players.ContainsKey(accountId))
                    {
                        return null;
                    }
                    T player = null;
                    if (!_players.TryGetValue(accountId, out player))
                    {
                        return null;
                    }

                    AccountDb oldAccount = player.Account;
                    AccountDb account = _accountDAO.GetAccount(accountId, oldAccount.UserName);

                    //update account
                    if (Monitor.TryEnter(oldAccount, 5000))
                    {
                        try
                        {
                            Copy(account, oldAccount);
                            return account;
                        }
                        finally
                        {
                            Monitor.Exit(oldAccount);
                        }
                    }
                }
                catch (Exception ex)
                {
                    NLogManager.PublishException(ex);
                }
                return null;
            }

            #endregion update_from_db

            #region util_methods

            public bool UpdateMoney(long accountId, long change, GameMoneyType moneyType)
            {
                if (accountId < 1 || change == 0)
                {
                    return false;
                }
                T player = GetPlayer(accountId);
                if (player == null)
                {
                    return false;
                }

                AccountDb account = player.Account;
                if (!Monitor.TryEnter(account, 5000)) return false;
                try
                {
                    long currentMoney = (moneyType == GameMoneyType.COIN) ? account.Coin : account.TotalStar;
                    if (change < 0 && Math.Abs(change) > currentMoney)
                    {
                        return false;
                    }
                    currentMoney += change;

                    switch (moneyType)
                    {
                        case GameMoneyType.COIN:
                            account.Coin = currentMoney;
                            break;
                        case GameMoneyType.TOTALSTAR:
                            account.TotalStar = currentMoney;
                            break;
                        default:
                            return false;
                    }

                    return true;
                }
                finally
                {
                    Monitor.Exit(account);
                }
            }

            /// <summary>
            /// Copy from first to second.
            /// </summary>
            /// <param name="first"></param>
            /// <param name="second"></param>
            public void Copy(AccountDb first, AccountDb second)
            {
                second.UserName = first.UserName;
                second.Coin = first.Coin;
                second.TotalStar = first.TotalStar;
                second.Star = first.Star;
                second.Vcoin = first.Vcoin;
                second.EventCoin = first.EventCoin;
            }

            public JToken Clone(T player)
            {
                try
                {
                    var obj = JToken.FromObject(player);
                    //mask username
                    if (obj["Account"] == null) return obj;
                    string username = obj["Account"]["UserName"].ToString();
                    username = StringUtil.MaskUserName(username);
                    obj["Account"]["UserName"].Replace(JToken.FromObject(username));
                    return obj;
                }
                catch (Exception ex)
                {
                    NLogManager.PublishException(ex);
                }
                return null;
            }

            public AccountDb GetAccount(long accountId)
            {
                if (accountId < 1)
                {
                    return null;
                }

                T player = null;
                var obj= _players.TryGetValue(accountId, out player) ? new AccountDb(player.Account) : null;
                return obj;
            }

            public bool Contains(string username, string password)
            {
                try
                {
                    int eventCoin = 0;
                    int isOtp = 0;
                    var serviceID = int.Parse(ConfigurationManager.AppSettings["SSO_SERVICE_ID"]);
                    long accountId = _accountDAO.Authentication(serviceID, DateTime.Now.Ticks.ToString(), username,
                        password, IPAddressHelper.GetClientIP(), out eventCoin, out isOtp);
                    //long accountId = _accountDAO.GetAccountID(username, password);
                    return Contains(accountId);
                }
                catch (Exception ex)
                {
                    NLogManager.PublishException(ex);
                }
                return false;
            }

            public bool Contains(string username, string password, out long retAccountID, out int coin)
            {
                retAccountID = -1;
                coin = 0;
                try
                {
                    //long accountId = _accountDAO.GetAccountID(username, password);
                    var serviceID = int.Parse(ConfigurationManager.AppSettings["SSO_SERVICE_ID"]);
                    int _coin;
                    int _isOtp;
                    long accountId = _accountDAO.Authentication(serviceID, DateTime.Now.Ticks.ToString(), username,
                        password, IPAddressHelper.GetClientIP(), out _coin, out _isOtp);
                    coin = _coin;
                    retAccountID = accountId;
                    return Contains(accountId);
                }
                catch (Exception ex)
                {
                    NLogManager.PublishException(ex);
                }
                return false;
            }

            public AccountDb Login(string username, string password)
            {
                if (String.IsNullOrEmpty(password) || String.IsNullOrEmpty(username))
                {
                    return null;
                }
                AccountDb account = null;
                try
                {
                    var serviceID = int.Parse(ConfigurationManager.AppSettings["SSO_SERVICE_ID"]);
                    account = _accountDAO.Login(serviceID, DateTime.Now.Ticks.ToString(), IPAddressHelper.GetClientIP(),
                        username, password);
                    //account = _accountDAO.Login(username, password);

                    if (account != null)
                    {
                        if (account.AccountID <= 0)
                        {
                            return null;
                        }
                        T player = AddPlayer(account);
                        if (player != null)
                        {
                            account = new AccountDb(player.Account);
                        }
                    }
                }
                catch (Exception ex)
                {
                    NLogManager.PublishException(ex);
                }
                return account;
            }

            public AccountDb Logout(long accountId)
            {
                T player = null;
                if (!_players.TryGetValue(accountId, out player)) return null;
                player.ChangeConnectionStatus(ConnectionStatus.DISCONNECTED);
                if (player.SessionID > 0)
                {
                    SedieRoom session = SessionHandler.Instance.GetSession(player.SessionID);
                    if (session != null)
                    {
                        if (session.IsPlaying)
                        {
                            session.RegisterLeaveGame(accountId);
                            return new AccountDb(player.Account);
                        }
                        else //neu phien dang ko choi, tinh toan xong
                        {
                            if (session.IsEnough)
                            {
                                session.RegisterLeaveGame(accountId);
                                return new AccountDb(player.Account);
                            }
                            else
                            {
                                session.RemovePlayer(accountId);
                            }
                        }
                    }
                    else //neu phien da bi huy, remove
                    {
                        if (_players.TryRemove(accountId, out player))
                        {
                            return new AccountDb(player.Account);
                        }
                    }
                }
                else //neu nguoi choi ko o trong phien nao, remove
                {
                    if (_players.TryRemove(accountId, out player))
                    {
                        return new AccountDb(player.Account);
                    }
                }
                return null;
            }

            #endregion util_methods
        }
    }
}